*! event_plot: Plot coefficients from a staggered adoption event study analysis
*! Version: June 1, 2021
*! Author: Kirill Borusyak
*! Please check the latest version at https://github.com/borusyak/did_imputation/
*! Citation: Borusyak, Jaravel, and Spiess, "Revisiting Event Study Designs: Robust and Efficient Estimation" (2021)
program define event_plot
version 13.0
syntax [anything(name=eqlist)] [, trimlag(numlist integer) trimlead(numlist integer) default_look stub_lag(string) stub_lead(string) plottype(string) ciplottype(string) together ///
		graph_opt(string asis) noautolegend legend_opt(string) perturb(numlist) shift(numlist integer) ///
		lag_opt(string) lag_ci_opt(string) lead_opt(string) lead_ci_opt(string) ///
		lag_opt1(string) lag_ci_opt1(string) lead_opt1(string) lead_ci_opt1(string) ///
		lag_opt2(string) lag_ci_opt2(string) lead_opt2(string) lead_ci_opt2(string) ///
		lag_opt3(string) lag_ci_opt3(string) lead_opt3(string) lead_ci_opt3(string) ///
		lag_opt4(string) lag_ci_opt4(string) lead_opt4(string) lead_ci_opt4(string) ///
		lag_opt5(string) lag_ci_opt5(string) lead_opt5(string) lead_ci_opt5(string) ///
		lag_opt6(string) lag_ci_opt6(string) lead_opt6(string) lead_ci_opt6(string) ///
		lag_opt7(string) lag_ci_opt7(string) lead_opt7(string) lead_ci_opt7(string) ///
		lag_opt8(string) lag_ci_opt8(string) lead_opt8(string) lead_ci_opt8(string) ///
		savecoef reportcommand noplot verbose alpha(real 0.05)]
qui {	
	// to-do: read dcdh or K_95; compatibility with the code from Goodman-Bacon, eventdd(?), did_multiplegt; use eventstudy_siegloch on options for many graphs; Burtch: ib4.rel_period_pos
	// Part 1: Initialize
	local verbose = ("`verbose'"=="verbose")
	if ("`plottype'"=="") local plottype connected
	if ("`ciplottype'"=="" & ("`plottype'"=="connected" | "`plottype'"=="line")) local ciplottype rarea
	if ("`ciplottype'"=="" & "`plottype'"=="scatter") local ciplottype rcap
	if (`verbose') noi di "#1"
	if ("`eqlist'"=="") local eqlist .
	if ("`shift'"=="") local shift 0
	if ("`savecoef'"=="savecoef") cap drop __event*

	tempname dot bmat Vmat bmat_current Vmat_current
	cap estimates store `dot' // cap in case there are no current estimate (but plotting is done based on previously saved ones)
		local rc_current = _rc
	local eq_n : word count `eqlist'
	if (`eq_n'>8) {
	    di as error "Combining at most 8 graphs are currently supported"
		error 198
	}
	
	if ("`perturb'"=="") {
	    local perturb 0
		if (`eq_n'>1) forvalues eq=1/`eq_n' {
			local perturb `perturb' `=0.2*`eq'/`eq_n''
		}
	}
	
	tokenize `eqlist'
	forvalues eq = 1/`eq_n' {
		local hashpos = strpos("``eq''","#")
	    if (`hashpos'==0) {  // e() syntax
			if ("``eq''"==".") {
				if (`rc_current'==0) estimates restore `dot'
					else error 301
			}
			else estimates restore ``eq''
			
			matrix `bmat' = e(b)
			cap matrix `Vmat' = e(V)
			if (_rc==0) local vregime = "matrix"
				else local vregime = "none"
		}
		else { // bmat#Vmat syntax
			matrix `bmat' = `=substr("``eq''",1,`hashpos'-1)'
			if (colsof(`bmat')==1) matrix `bmat' = `bmat''
			
			cap matrix `Vmat' = `=substr("``eq''",`hashpos'+1,.)'
			if (_rc==0) {
				if (rowsof(`Vmat')==1) local vregime = "row"
				else if (colsof(`Vmat')==1) {
					matrix `Vmat' = `Vmat''
					local vregime = "row"
				}
				else if (rowsof(`Vmat')==colsof(`Vmat')) local vregime = "matrix"
				else {
					di as error "The variance matrix " substr("``eq''",`hashpos'+1,.) " does not have an expected format in model `eq'"
					error 198
				}
			}
			else local vregime = "none"
		}
			
		* extract prefix and suffix
		foreach o in lag lead {
		    local currstub_`o' : word `eq' of `stub_`o''
			if ("`currstub_`o''"=="") local currstub_`o' : word 1 of `stub_`o''
			if ("`currstub_`o''"=="" & e(cmd)=="did_imputation" & "`o'"=="lag") local currstub_`o' tau#
			if ("`currstub_`o''"=="" & e(cmd)=="did_imputation" & "`o'"=="lead") local currstub_`o' pre#
			
			if ("`currstub_`o''"!="") {
				local hashpos = strpos("`currstub_`o''","#")
				if (`hashpos'==0) {
					di as error "stub_`o' is incorrectly specified for model `eq'"
					error 198
				}
				local prefix_`o' = substr("`currstub_`o''",1,`hashpos'-1)
				local postfix_`o' = substr("`currstub_`o''",`hashpos'+1,.)
				local lprefix_`o' = length("`prefix_`o''")
				local lpostfix_`o' = length("`postfix_`o''")
				local have_`o' = 1
			}
			else local have_`o' = 0
		}
		if (`have_lag'==0 & `have_lead'==0) {
			di as error "At least one of stub_lag and stub_lead has to be specified for model `eq'"
			error 198
		}
		if ("`currstub_lag'"=="`currstub_lead'") {
			di as error "stub_lag and stub_lead have to be different for model `eq'"
			error 198
		}
		
		// Part 2: Compute the number of available lags&leads
		local maxlag = -1
		local maxlead = 0 // zero leads = nothing since they start from 1, while lags start from 0
		local allvars : colnames `bmat'
		foreach v of local allvars {
			if (`have_lag') {
				if (substr("`v'",1,`lprefix_lag')=="`prefix_lag'" & substr("`v'",-`lpostfix_lag',.)=="`postfix_lag'") {
					if !mi(real(substr("`v'",`lprefix_lag'+1,length("`v'")-`lprefix_lag'-`lpostfix_lag'))) {
						local maxlag = max(`maxlag',real(substr("`v'",`lprefix_lag'+1,length("`v'")-`lprefix_lag'-`lpostfix_lag')))
					}
				}
			}
			if (`have_lead') {
				if (substr("`v'",1,`lprefix_lead')=="`prefix_lead'" & substr("`v'",-`lpostfix_lead',.)=="`postfix_lead'") {
					if !mi(real(substr("`v'",`lprefix_lead'+1,length("`v'")-`lprefix_lead'-`lpostfix_lead'))) {
						local maxlead = max(`maxlead',real(substr("`v'",`lprefix_lead'+1,length("`v'")-`lprefix_lead'-`lpostfix_lead')))
					}
				}
			}
		}
		
		local curr_trimlag : word `eq' of `trimlag'
			if mi("`curr_trimlag'") local curr_trimlag : word 1 of `trimlag'
			if mi("`curr_trimlag'") local curr_trimlag = -2
		local curr_trimlead : word `eq' of `trimlead'
			if mi("`curr_trimlead'") local curr_trimlead : word 1 of `trimlead'
			if mi("`curr_trimlead'") local curr_trimlead = -1
		
		local maxlag = cond(`curr_trimlag'>=-1, min(`maxlag',`curr_trimlag'), `maxlag') 
		local maxlead = cond(`curr_trimlead'>=0, min(`maxlead',`curr_trimlead'), `maxlead')
		if (_N<`maxlag'+`maxlead'+1) {
			di as err "Not enough observations to store `=`maxlag'+`maxlead'+1' coefficient estimates for model `eq'"
			error 198
		}
		if (`verbose') noi di "#2 Model `eq': `maxlag' lags, `maxlead' leads"

		// Part 3: Fill in coefs & CIs
		if ("`savecoef'"=="") tempvar H`eq' pos`eq' coef`eq' hi`eq' lo`eq'
		else {
			local H`eq' __event_H`eq'
			local pos`eq' __event_pos`eq'
			local coef`eq' __event_coef`eq'
			local hi`eq' __event_hi`eq'
			local lo`eq' __event_lo`eq'
		}
		
		local shift`eq' : word `eq' of `shift'
		if ("`shift`eq''"=="") local shift`eq' 0
		
		gen `H`eq'' = _n-1-`maxlead' if _n<=`maxlag'+`maxlead'+1
		gen `coef`eq'' = .
		gen `hi`eq'' = .
		gen `lo`eq'' = .
		label var `H`eq'' "Periods since treatment"
		if (`maxlag'>=0) forvalues h=0/`maxlag' {
			matrix `bmat_current' = J(1,1,.)
			cap matrix `bmat_current' = `bmat'[1,"`prefix_lag'`h'`postfix_lag'"]
			cap replace `coef`eq'' = `bmat_current'[1,1] if `H`eq''==`h' // because `bmat'[1,"`prefix_lag'`h'`postfix_lag'"] is only a matrix expression on macs			
			
			if ("`ciplottype'"!="none" & "`vregime'"!="none") {
				matrix `Vmat_current' = J(1,1,.)
				if ("`vregime'"=="matrix") cap matrix `Vmat_current' = `Vmat'["`prefix_lag'`h'`postfix_lag'","`prefix_lag'`h'`postfix_lag'"]
					else cap matrix `Vmat_current' = `Vmat'[1,"`prefix_lag'`h'`postfix_lag'"]
				local se = `Vmat_current'[1,1]^0.5
				cap replace `hi`eq'' = `bmat_current'[1,1]+invnorm(1-`alpha'/2)*`se' if `H`eq''==`h'
				cap replace `lo`eq'' = `bmat_current'[1,1]-invnorm(1-`alpha'/2)*`se' if `H`eq''==`h'
			}
		}
		if (`maxlead'>0) forvalues h=1/`maxlead' {
			matrix `bmat_current' = J(1,1,.)
			cap matrix `bmat_current' = `bmat'[1,"`prefix_lead'`h'`postfix_lead'"]
			cap replace `coef`eq'' = `bmat_current'[1,1] if `H`eq''==-`h'
			
			if ("`ciplottype'"!="none" & "`vregime'"!="none") {
				matrix `Vmat_current' = J(1,1,.)
				if ("`vregime'"=="matrix") cap matrix `Vmat_current' = `Vmat'["`prefix_lead'`h'`postfix_lead'","`prefix_lead'`h'`postfix_lead'"]
					else cap matrix `Vmat_current' = `Vmat'[1,"`prefix_lead'`h'`postfix_lead'"]
				local se = `Vmat_current'[1,1]^0.5
				cap replace `hi`eq'' = `bmat_current'[1,1]+invnorm(1-`alpha'/2)*`se' if `H`eq''==-`h'
				cap replace `lo`eq'' = `bmat_current'[1,1]-invnorm(1-`alpha'/2)*`se' if `H`eq''==-`h'
			}
		}
		count if !mi(`coef`eq'')
		if (r(N)==0) {
			if (`eq_n'==1) noi di as error `"No estimates found. Make sure you have specified stub_lag and stub_lead correctly."'
				else noi di as error `"No estimates found for the model "``eq''". Make sure you have specified stub_lag and stub_lead correctly."'
			error 498
		}
		if (`verbose') noi di "#3 `perturb'"
		
		local perturb_now : word `eq' of `perturb'
		if ("`perturb_now'"=="") local perturb_now = 0
		if (`verbose') noi di "#3A gen `pos`eq''=`H`eq''+`perturb_now'-`shift`eq''"
		gen `pos`eq''=`H`eq''+`perturb_now'-`shift`eq''
		if (`verbose') noi di "#3B"

	}
	cap estimates restore `dot'
	cap estimates drop `dot'
	
	// Part 4: Prepare graphs
	if ("`default_look'"!="") {
		local graph_opt xline(0, lcolor(gs8) lpattern(dash)) yline(0, lcolor(gs8)) graphregion(color(white)) bgcolor(white) ylabel(, angle(horizontal)) `graph_opt'
		if (`eq_n'==1) {
			local lag_opt color(navy) `lag_opt'
			local lead_opt color(maroon) msymbol(S) `lead_opt'
			local lag_ci_opt color(navy%45 navy%45) `lag_ci_opt' // color repeated twice only for connected/scatter, o/w doesn't matter
			local lead_ci_opt color(maroon%45 maroon%45) `lead_ci_opt'
		}
		else {
			local lag_opt1 color(navy) `lag_opt1'
			local lag_opt2 color(maroon) `lag_opt2'
			local lag_opt3 color(forest_green) `lag_opt3'
			local lag_opt4 color(dkorange) `lag_opt4'
			local lag_opt5 color(teal) `lag_opt5'
			local lag_opt6 color(cranberry) `lag_opt6'
			local lag_opt7 color(lavender) `lag_opt7'
			local lag_opt8 color(khaki) `lag_opt8'
			local lead_opt1 color(navy) `lead_opt1'
			local lead_opt2 color(maroon) `lead_opt2'
			local lead_opt3 color(forest_green) `lead_opt3'
			local lead_opt4 color(dkorange) `lead_opt4'
			local lead_opt5 color(teal) `lead_opt5'
			local lead_opt6 color(cranberry) `lead_opt6'
			local lead_opt7 color(lavender) `lead_opt7'
			local lead_opt8 color(khaki) `lead_opt8'
			local lag_ci_opt1 color(navy%45 navy%45) `lag_ci_opt1'
			local lag_ci_opt2 color(maroon%45 maroon%45) `lag_ci_opt2'
			local lag_ci_opt3 color(forest_green%45 forest_green%45) `lag_ci_opt3'
			local lag_ci_opt4 color(dkorange%45 dkorange%45) `lag_ci_opt4'
			local lag_ci_opt5 color(teal%45 teal%45) `lag_ci_opt5'
			local lag_ci_opt6 color(cranberry%45 cranberry%45) `lag_ci_opt6'
			local lag_ci_opt7 color(lavender%45 lavender%45) `lag_ci_opt7'
			local lag_ci_opt8 color(khaki%45 khaki%45) `lag_ci_opt8'
			local lead_ci_opt1 color(navy%45 navy%45) `lead_ci_opt1'
			local lead_ci_opt2 color(maroon%45 maroon%45) `lead_ci_opt2'
			local lead_ci_opt3 color(forest_green%45 forest_green%45) `lead_ci_opt3'
			local lead_ci_opt4 color(dkorange%45 dkorange%45) `lead_ci_opt4'
			local lead_ci_opt5 color(teal%45 teal%45) `lead_ci_opt5'
			local lead_ci_opt6 color(cranberry%45 cranberry%45) `lead_ci_opt6'
			local lead_ci_opt7 color(lavender%45 lavender%45) `lead_ci_opt7'
			local lead_ci_opt8 color(khaki%45 khaki%45) `lead_ci_opt8'
		}
		local legend_opt region(lstyle(none)) `legend_opt'
	}
	
	local plotindex = 0
	local legend_order

	forvalues eq = 1/`eq_n' {
	    local lead_cmd
		local leadci_cmd
		local lag_cmd
		local lagci_cmd
		
		if ("`together'"=="") { // lead graph commands only when they are separate from lags
			count if !mi(`coef`eq'') & `H`eq''<0
			if (r(N)>0) {
				local ++plotindex
				local lead_cmd (`plottype' `coef`eq'' `pos`eq'' if !mi(`coef`eq'') & `H`eq''<0, `lead_opt' `lead_opt`eq'')
				local legend_order = `"`legend_order' `plotindex' "Pre-trend coefficients""'
			}

			count if !mi(`hi`eq'') & `H`eq''<0
			if (r(N)>0) {
				local ++plotindex
				local leadci_cmd (`ciplottype' `hi`eq'' `lo`eq'' `pos`eq'' if !mi(`hi`eq'') & `H`eq''<0, `lead_ci_opt' `lead_ci_opt`eq'')
			}
		}
		
		local lag_filter = cond("`together'"=="", "`H`eq''>=0", "1") 
		count if !mi(`coef') & `lag_filter'
		if (r(N)>0) {
			local ++plotindex
			local lag_cmd (`plottype' `coef`eq'' `pos`eq'' if !mi(`coef`eq'') & `lag_filter', `lag_opt' `lag_opt`eq'')
			if ("`together'"=="") local legend_order = `"`legend_order' `plotindex' "Treatment effects""'
		}

		count if !mi(`hi`eq'') & `lag_filter'
		if (r(N)>0) {
			local ++plotindex
			local lagci_cmd (`ciplottype' `hi`eq'' `lo`eq'' `pos`eq'' if !mi(`hi`eq'') & `lag_filter', `lag_ci_opt' `lag_ci_opt`eq'')
		}
		if ("`autolegend'"=="noautolegend") local legend = "" 
			else if ("`together'"=="together") local legend = "legend(off)" // show auto legend only for separate, o/w just one item
			else local legend legend(order(`legend_order') `legend_opt')
		local maincmd `maincmd' `lead_cmd' `leadci_cmd' `lag_cmd' `lagci_cmd'
		if (`verbose') noi di `"#4a ``eq'': `lead_cmd' `leadci_cmd' `lag_cmd' `lagci_cmd'"'
	}
	if (`verbose' | "`reportcommand'"!="") noi di `"twoway `maincmd' , `legend' `graph_opt'"'
	if ("`plot'"!="noplot") twoway `maincmd', `legend' `graph_opt'
}
end
